Automaty komórkowe 1.0 to rozbudowany program do symulacji komputerowej automatów komórkowych, a to jest jego dokumentacja. Dokumentacja to nie jest dobre miejsce na wprowadzenie do tej fascynującej dziedziny, więc jeśli nie wiesz jeszcze, co to takiego, poszukaj czegoś na ten temat.
Program powstał do celów prezentacyjnych, jako dodatek do mojego referatu pt. "Automaty komórkowe i ich zastosowania", który napisałem na II Ogólnopolską Konferencję Twórców Gier Komputerowych, która odbyła się w dniach 1-3 kwietnia 2005 roku w Akademii Podlaskiej w Siedlcach.
Umożliwia on implementowanie różnego rodzaju automatów komórkowych poprzez pisanie kodu przeliczającego w języku skryptowym, a komórki mogą być wizualizowane na różne sposoby. Oprócz przeglądania dołączonych do niego, przykładowych skryptów umożliwia on więc także własną zabawę z automatami komórkowymi. Dlatego zdecydowałem się go udostępnić.
Jedno trzeba przyznać - program nie grzeszy szybkością działania. W niektórych przypadkach nie pozwala na wykonywanie więcej, niż kilku, kilkunastu czy kilkudziesięciu iteracji na sekundę. (w innych zaś można wykonywać ich nawet 100'000). Niestety, taka jest cena, jaką płaci się za elastyczność. Interpretacja języka skryptowego nigdy nie będzie tak szybka, jak wykonywanie prawdziwego kodu.
Do napisania programu zostały użyte:
| Biblioteka multimedialna | Microsoft DirectX 9.0c |
| Język skryptowy | AngelCode Spripting Library (AngelScript) 1.10.1c |
| Biblioteka do obsługi formatów graficznych | DevIL 1.6.5 |
| Biblioteka do kompresji danych | zlib 1.2.1 |
| Moja biblioteka uniwersalna i parser XNL | pxlib 2.3.2 |
| Mój szkielet | Engine 3 |
Program rozpowszechniany jest na licencji GNU GPL. Oznacza m.in., że:
Kwestia źródeł jest o tyle istotna, że wraz z programem po raz pierwszy udostępniłem mój Engine 3 - rozbudowany szkielet aplikacji DirectX i inne moduły, które są dla mnie (w chwili pisania tych słów) dużym osiągnięciem programistycznym. Większość kodu jest dobrze udokumentowana w formacie Doxygen. Należą do niego pliki:
| Base | Moduł bazowy z różnymi podstawowymi elementami |
| Error | Moduł do obsługi błędów |
| Framework | Szkielet aplikacji DirectX |
| Gfx | Moduł do wyświetlania grafiki 2D (w tym dość potężny Font Manager) |
| GUI | Moduł bazowy graficznego interfejsu użytkownika |
| Stream | Moduł z moją realizacją strumieni danych binarnych |
Tak więc masz dostęp do kompletnego źródła Engine 3 jako do części tego programu na licencji GNU GPL. Możesz je przeglądać i swobodnie uczyć się z nich. Jeśli chcesz zastosować te moduły czy inne większe fragmenty jego kodu w swoim projekcie, obowiązują cię ograniczenia zasady copyleft. Możesz to robić do celów edukacyjnych. Jeśli jednak chciałbyś ukończyć i opublikować swoją produkcję i używasz w niej choćby fragmentu moich źródeł, cała twoja produkcja musi być również wolnym oprogramowaniem (na licencji GNU lub innej kompatybilnej).
Automatami komórkowymi interesuję się po trochu od lat zbierając różne artykuły i inne materiały. Napisałem kiedyś wygaszacz ekranu z nimi (ProgrameX Cellular Automat ScreenSaver), a teraz mam zamiar zastosować je do symulacji wiatru w mojej grze RTS - SnowCraft 2.
Poprzednia wersja mojego artykułu na ten temat ma już kilka lat i teraz wiem na ten temat dużo więcej, niż wiedziałem wtedy. Dlatego mam nadzieję, że referat, do którego napisałem ten program, dobrze go zastąpi.
Program miał być tylko dodatkiem do referatu. Bardzo szybko jednak postanowiłem zaprojektować i napisać go tak rozbudowanym, by nadawał się do opublikowania jako osobna produkcja. Decyzja o tym, jak również o upublicznieniu jego źródeł (które, nawiasem mówiąc, skupione w jednym pliku main.cpp nie są najwyższej jakości :) nie była łatwa.
Mam jednak nadzieję, że dzięki niej przynajmniej kilka osób zainteresuje się automatami komórkowymi, nauczy się niełatwej niestety obsługi tego programu, pobawi się nim trochę i być może kiedyś nawet zastosuje automaty komórkowe w swoich grach.
Adam Sawicki "Regedit"| Strona domowa | http://www.regedit.risp.pl |
| ProgrameX | http://www.programex.prv.pl |
| regedit_USUN_@risp.pl (usuń to _USUN_ :) | |
| IRC | #programex i #warsztat w sieci PolNet |
| Gadu-Gadu | 2272474 |
| [ESC] | Zatrzymaj |
| [Shift]+[ESC] | Wyzeruj licznik iteracji |
| [Spacja] | Wykonaj jeden krok |
| [Enter] | Uruchom |
| [Ctrl]+[Enter] | Przełącz na pełny ekran lub z powrotem do okna |
| [Ctrl]+[O] | Wczytaj tablicę |
| [Ctrl]+[S] | Zapisz tablicę |
| [Ctrl]+[Shift]+[O] | Wczytaj obrazek |
| [Ctrl]+[Shift]+[S] | Zapisz obrazek |
| [1]..[0] | Przyciski użytkownika |
| [F] | Filtruj teksturę |
| [T] | Rozłóż teksturę sąsiadująco |
| [+], [-] | Zmień powiększenie |
| [*] | Zresetuj powiększenie |
| [lewo], [prawo] | Zmień prędkość |
| [góra], [dół] | Wybierz rozdzielczość |
Program działa w oparciu o pliki AK. Bez takiego pliku nie da się go uruchomić. Program można uruchamiać na dwa sposoby:
a_komorkowe.exe. Pokazane zostanie
wtedy okno, w którym można będzie wybrać plik AK.
Jeśli wybrany plik AK nie będzie poprawny lub wystąpi inny błąd podczas uruchamiania albo działania:
Error.txt,
Program wykorzystuje DirectX i przez cały czas nieustannie pętli się
wykorzystując 100% mocy procesora. To nic strasznego - tak robią wszystkie gry.
Okno programu można swobodnie rozszerzać, maksymalizować i minimalizować.
Kiedy zminimalizowany, program przestaje się pętlić i zatrzymuje swoje
działanie. Dostępny jest też tryb pełnoekranowy. Program poprawnie powraca
po wyjściu w takim trybie do systemu kombinacją klawiszy
[Alt]+[Tab] czy [Ctrl]+[ESC].
Podczas działania pracą programu można sterować za pomocą kontrolek umieszczonych na pasku w górnej części.
Do uruchomienia obliczeń służy przycisk Uruchom, a do
zatrzymania Zatrzymaj.
Running
Opcjonalna
Typ: logiczny
Domyślnie: false
Ta sekcja pierwszej części pliku AK określa, czy obliczenia mają być uruchomione automatycznie natychmiast po uruchomieniu programu i wczytaniu pliku AK, czy użytkownik będzie musiał za każdym razem uruchomić ten proces ręcznie (dokonując wcześniej ewentualnych przygotowań).
Przykład:
Running = true
FPS to liczba klatek na sekundę (ang. "frames per second"). Obrazuje ona szybkość pracy programu. Na każdą klatkę składają się obliczenia związane z automatem komórkowym oraz odrysowanie zawartości okna. Liczba klatek na sekundę jest zależna od szybkości procesora, karty graficznej, a szczególnie od aktualnego obciążenia programu obliczeniami. Jeśli FPS jest małe, program chodzi bardziej skokowo i jego interfejs nieco wolniej reaguje na polecenia.
IPS to liczba iteracji na sekundę. Obrazuje szybkość wykonywania
obliczeń na automacie komórkowym. Można ją zmieniać za pomocą suwaka
Zmień prędkość w skali logarytmicznej w zakresie od
1 iteracji na 10 sekund aż do 100'000 iteracji na sekundę. Im większa prędkość,
tym mniej FPS. Na suwaku tym zaznaczone są kolorami pewne wartości:
Obszar żółty to obszar powyżej liczby klatek na sekundę (FPS). Ustawienie wartości w tym przedziale nie robi w zasadzie żadnej różnicy względem obszaru zielonego oprócz tego, że program będzie wykonywał więcej obliczeń, niż odrysowań na sekundę. Nie będziesz mógł wtedy zobaczyć na ekranie wyników obliczeń każdej iteracji.
Obszar czerwony to limit ilości iteracji na sekundę, powyżej którego komputer nie nadąża. Na początku jest nieznany. Dopiero jego przekroczenie powoduje zadziałanie zabezpieczenia i ustalenie tej wartości. Zabezpieczenie to wykrywa sytuację, w której program ma do wykonania więcej iteracji na sekundę, niż jest w stanie wykonać. W każdej klatce działania programu zbiera się wtedy coraz więcej iteracji do wykonania i gdyby zabezpieczenie nie zadziałało, program w końcu całkowicie by się zawiesił. Dzięki niemu wartość IPS zostaje już po kilku klatkach ustawiona na bezpieczną wartość bliską maksymalnej.
Limit IPS zależy od szybkości procesora, ale przede wszystkim od algorytmu przeliczania automatu komórkowego zawartego pliku AK. Jeśli w każdej iteracji przeliczane są wszystkie komórki, maksymalna wartość IPS może wynosić kilkadziesiąt. Jeśli przeliczana jest tylko jedna, program może być w stanie wykonać nawet pełne 100'000 iteracji na sekundę.
Speed
Opcjonalna
Typ: liczba rzeczywista
Domyślnie: 1.0
Ta sekcja określa początkową prędkość aktualizacji w iteracjach na sekundę (IPS), jaka będzie ustawiona w programie po uruchomieniu i wczytaniu danego pliku AK.
Przykład:
Speed = 10.0
Możesz też wykonywać automat komórkowy krok po kroku używając polecenia
Wykonaj jeden krok.
Ostatnia z liczb wyświetlanych na panelu górnym to licznik iteracji.
Do jego wyzerowania służy przycisk Wyzeruj licznik iteracji.
Przycisk Wykonaj daną liczbę iteracji pozwala
z kolei na wykonanie określonej ilości obliczeń. Kolejne iteracje wykonywane są
w maksymalnym możliwym tempie, a efekty obliczeń są często wyświetlane
na ekranie w postaci kolejnych klatek.
Podstawą działania programu jest wykonywanie podanego podczas uruchomienia pliku AK. Przygotowanie takiego pliku jest z kolei podstawą całej zabawy z programem, jeśli tylko nie będzie się ona ograniczała wyłącznie do przeglądania plików przygotowanych przez innych.
Plik AK to plik tekstowy. Znaki końca wiersza mogą być kodowane w dowolny sposób, ale domyślne zaleca się windowsowe CR+LF. Kodowanie znaków powinno być jednobajtowe. W zakresie znaków ANSI kompatybilne są ze sobą różne kodowania, w tym Windows-1250, ISO-8859-1, ISO-8859-2 i UTF-8. Jeśli zachodzi potrzeba kodowania polskich liter, należy użyć kodowania Windows-1250 (CP1250).
Plik AK składa się z dwóch osobnych części oddzielonych napisem:
--CODE--
| Część I | Język opisu XNL | Opisuje parametry automatu komórkowego |
| Część II | Język skryptowy AngelScript | Zawiera kod do wykonania przez program |
W językach używanych w obydwu tych częściach obowiązuje wolny format zapisu i rozróżniana jest wielkość liter.
Pierwsza część pliku AK opisuje parametry automatu komórkowego. Jest w języku XNL. XNL (Extensible Description Language) to język opisu opracowany przeze mnie w wyniku niezadowolenia z popularnego standardu XML. Parser do niego wchodzi w skład mojej biblioteki pxlib dostępnej na licencji GNU LGPL. Pełną specyfikację języka możesz naleźć na stronie pxxnl. Tutaj przedstawię uproszczone podstawy jego składni.
Podstawą XNL są elementy, czyli napisy oddzielane dowolnym odstępem
(spacjami, tabulacjami, końcami wiersza), przecinkiem , lub
średnikiem ;.
Jeśli element zawiera spacje lub inne znaki specjalne, należy go objąć
w apostrofy '' lub cudzysłowy "".
Do takich znaków specjalnych należą separatory atrybutów - znak równości
= lub dwukropek :. Specjalne znaczenie mają też
nawiasy kwadratowe [] i klamrowe {}, które wyznaczają
początek i koniec sekcji.
Jak widać, każdy z tych rodzajów tokenów w języku XNL może być zapisywany na conajmniej dwa sposoby. Choć w rzeczywistości istnieje możliwość ich rozróżniania, opisywany program tego nie robi. Można więc używać ich zamiennie.
Komentarze wstawia się tak:
[/ Komentarz... /]
Pierwsza część pliku AK musi rozpoczynać się takim nagłówkiem:
[Automat komorkowy 1]
Dalej następuje zbiór sekcji w postaci:
<nazwa> = <wartość>
Jeśli jakaś sekcja występuje więcej niż raz, efekt jest niezdefiniowany (ang. UB - Undefined Behaviour). Kiedy coś jest niezdefiniowane, nie można tak robić, bo może się stać wszystko - łącznie z eksplozją Księżyca bądź śmiercią programisty :)
Opisy dopuszczalnych sekcji, ich nazwy, składnia wartości i inne dane są rozsiane po tym dokumencie. Ich pełną listę znajdziesz w Indeksie sekcji części I pliku AK. Tutaj przedstawiam jedną z nich:
Title
Obowiązkowa
Typ: łańcuch
Określa tytuł automatu komórkowego
Przykład:
Title = "Mrówka Lagtona"
Część II pliku AK to kod w języku skryptowym AngelScript. Ten kod zostaje wczytany podczas uruchamiania programu, jest kompilowany do specjalnego kodu binarnego, który program wykonuje potem w wirtualnej maszynie. Pełną dokumentację języka AngelScript znajdziesz na stronie Script Writer's manual. Tutaj przedstawię tylko podstawy jego składni.
AngelScript ma składnię wzorowaną na języku programowania C++.
Dostępne są więc wszelkie instrukcje sterujące, jak pętle for,
while i do...while, a także if,
switch itp. Tak samo, jak w C++ definiuje się też zmienne globalne,
zmienne lokalne i funkcje.
Język ten nie jest zorientowany obiektowo. W skrypcie nie ma więc możliwości tworzenia własnych klas. Nie ma też wskaźników ani łańcuchów znaków. Warto natomiast stosować przekazywanie parametrów przez wartość stałą - pomoże to kompilatorowi zoptymalizować kod:
void MojaFunkcja(const int Parametr1, const bool Parametr2)
{
...
}
Komentarze pisze się w tym jezyku tak samo, jak w C++:
// Komentarz jednolinijkowy /* Komentarz wielolinijkowy */
Dalsze szczegóły na temat skryptów są tematem całego następnego rozdziału.
Program oddaje do dyspozycji dla skryptu wiele funkcji służących do różnych operacji (np. matematycznych) oraz do komunikacji z przetwarzanym automatem komórkowym. Ze swej strony skrypt powinien udostępniać dla programu pewne funkcje, które program będzie wywoływał. Jedne z nich są wymagane, inne opcjonalne. Funkcje identyfikowane są po pełnym nagłówku (nazwa z rozróżnieniem wielkości liter, ilość i typy parametrów oraz typ wartości zwracanej).
Opisy jednych i drugich funkcji są porozrzucane po całej tej dokumentacji. Ich pełną listę znajdziesz w Indeksie funkcji części II pliku AK. Tutaj przedstawiam dwie podstawowe:
void OnInit()
Obowiązkowo udostępniana przez skrypt dla programu
Wywoływana jest jednokrotnie podczas uruchamiania programu, po wczytaniu pliku AK i zainicjowaniu wszelkich struktur danych. Może posłużyć do inicjalizacji skryptu - wstępnego wypełnienia tablicy i innych przygotowań.
Przykład:
void OnInit()
{
int x, y, w = GetWidth(), h = GetHeight();
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
SetBuffer_b1(x, y, false);
}
}
}
void OnFrame()
Obowiązkowo udostępniana przez skrypt dla programu
Wywoływana jest w każdej iteracji. Jej zadaniem jest przeprowadzanie właściwych obliczeń na tablicy komórek.
Przykład:
void OnFrame()
{
int x, y, w = GetWidth(), h = GetHeight();
uint count;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
count = 0;
if (GetBuffer_b1(x-1, y-1)) count++;
if (GetBuffer_b1(x , y-1)) count++;
if (GetBuffer_b1(x+1, y-1)) count++;
if (GetBuffer_b1(x-1, y )) count++;
if (GetBuffer_b1(x+1, y )) count++;
if (GetBuffer_b1(x-1, y+1)) count++;
if (GetBuffer_b1(x , y+1)) count++;
if (GetBuffer_b1(x+1, y+1)) count++;
if (count > 4)
SetBuffer_b1(x, y, true);
else if (count < 4)
SetBuffer_b1(x, y, false);
}
}
}
Podstawą działania skryptu jest oczywiście przetwarzanie pewnych danych. Dlatego najpierw trzeba poznać dostępne typy danych i możliwe operacje na nich.
W przeciwieństwie do większości języków skryptowych, w AngelScript zmienne trzeba jawnie deklarować i jawnie podawać ich typ, tak jak w C++ i innych normalnych językach programowania. Dostępne typy danych odpowiadają typom języka C++. Oto niektóre z nich:
| Typ AngelScript | Opis | Przykładowe wartości | Odpowiedniki w C++ |
|---|---|---|---|
| bool | Typ logiczny | false true | bool |
| int8 | Liczba 1-bajtowa ze znakiem | -128, 0, +33, 127 | char |
| int16 | Liczba 2-bajtowa ze znakiem | -32768, 0, 32767, 0x02 | short |
| int32, int | Liczba 4-bajtowa ze znakiem | -2147483648, +33, 2147483647, 0x02 | int, long |
| uint8 | Liczba 1-bajtowa bez znaku | 0, 33, 255, 0xFF | unsigned char, BYTE |
| uint16 | Liczba 2-bajtowa bez znaku | 0, 65535, 0xFFFF | unsigned short, WORD |
| uint32, uint | Liczba 4-bajtowa bez znaku | 33, 4294967295, 0xDEADCODE | unsigned, DWORD |
| float | Liczba zmiennoprzecinkowa | -10, 0.05, -2.5e-10 | float |
| bits8 | 1-bajtowy zbiór bitów | ||
| bits16 | 2-bajtowy zbiór bitów | ||
| bits32 | 4-bajtowy zbiór bitów |
W celu przechowywania w skrypcie wartości pomiędzy wywołaniami
OnInit i OnFrame (pomiędzy kolejnymi iteracjami)
można użyć zmiennych globalnych. Można też podczas definiowania nadawać im
określoną wartość początkową:
int Licznik = 0;
void OnFrame()
{
// ...
Licznik++;
}
AngelScript nie jest zbyt bogaty, jeśli chodzi o wbudowane struktury danych. Wynika to z jego założeń, elastyczności i dążenia do maksymalnej wydajności. Nie ma struktur dynamicznych (jak listy). Nie ma też struktur tak uniwersalnych i wygodnych w użyciu, jak znane choćby z PHP tablice asocjacyjne. Jedynym wbudowanym kontenerem jest zwykła, statyczna tablica o podanym rozmiarze. Oto przykład jej użycia:
int[] tab(4); tab[0] = 1; tab[1] = 2; tab[2] = 4; tab[3] = tab[0] * tab[1];
Rozmiar tablicy nie musi być znany w momencie kompilacji. Jako podany
w nawiasie w miejscu definicji tablicy rozmiar (tu 4) można użyć
zmiennej lub wyrażenia.
Dostępne są znane z C++ operatory arytmetyczne
+ - * / %, a także logiczne
|| && ! i bitowe & | ~ ^.
Podczas dzielenia zasada jest taka sama, jak w C++. Dzielenie dwóch liczb całkowitych to dzielenie całkowite - z obcięciem reszty. Jeśli choć jedna z liczb jest zmiennoprzecinkowa, dokonywane jest dzielenie zmiennoprzecinkowe. Dzielenie przez zero jest wykrywane przez program i powoduje jego awaryjne zakończenie z błędem.
W AngelScript bardzo użyteczne bywa rzutowanie typów. Jego składnia to
typ(wyrażenie).
Do dyspozycji skryptu oddane są dwie podstawowe stałe matematyczne w postaci funkcji zwracających stosowne wartości:
float PI()
float E()
Zwracają odpowiednio wartość stałej PI równej mniej więcej 3.14 oraz E równej mniej więcej 2.71.
Sam AngelScript nie ma wbudowanych żadnych funkcji, nawet podstawowych operacji matematycznych. Interpreter wbudowany w program wyposażyłem we wszystkie podstawowe funkcje tego rodzaju, jednak operujące tylko na liczbach zmiennoprzecinkowych pojedynczej precyzji.
Typ zmiennoprzecinkowy o podwójnej precyzji - double - jest
również dostępny w języku. Nie zawarłem go na liście typów, ponieważ nie oferuję
dla niego wsparcia w postaci takich właśnie funkcji.
int abs_i(int)
float abs_f(float)
Zwraca wartość bezwzględną podanej liczby.
uint min_u(uint, uint)
int min_i(int, int)
float min_f(float, float)
Zwraca mniejszą z podanych wartości.
uint max_u(uint, uint)
int max_i(int, int)
float max_f(float, float)
Zwraca większą z podanych wartości.
uint minmax_u(uint, uint, uint)
int minmax_i(int, int, int)
float minmax_f(float, float, float)
Zwraca wartość drugą ograniczoną do przedziału od pierwszej do trzeciej.
bool rand_b()
Zwraca losową wartość logiczną. Współczynnik wypełnienia to 50%.
uint rand_u()
Zwraca losową spośród wszystkich możliwych wartości liczby typu uint.
Rozkład równomierny.
float rand_f()
Zwraca losową wartość zmiennoprzecinkową z przedziału od 0.0 do 1.0.
Rozkład równomierny.
float rand_normal(float)
Zwraca wartość losową o rozkładzie normalnym, wartości oczekiwanej 0.0
i podanym odchyleniu standardowym (sigma).
uint rand_u_range(uint, uint)
float rand_f_range(float, float)
Zwraca losową wartość z podanego zakresu. Rozkład równomierny.
float sin(float)
float cos(float)
float tan(float)
Funkcje trygonometryczne. Kąt podaje się w radianach.
float asin(float)
float acos(float)
float atan(float)
Funkcje odwrotne do trygonometrycznych (arcus). Kąt jest w radianach.
float sinh(float)
float cosh(float)
float tanh(float)
Funkcje hiperboliczne. Kąt podaje się w radianach.
float atan2(float, float)
Zwraca kąt wektora o podanych współrzędnych x i y.
Odpowiada atan(y/x), ale nie występuje w nim dzielenie przez zero.
Kąt jest w radianach.
float ceil(float)
Zaokrągla podaną liczbę w górę.
float floor(float)
Zaokrągla podaną liczbę w dół.
int round(float)
Zaokrągla podaną liczbę do najbliższej liczby całkowitej.
Konwersję do liczby całkowitej bez zaokrąglania (z zaokrąglaniem w dół) przeprowadza się za pomocą zwykłego rzutowania wartości typu zmiennoprzecinkowego (rzeczywistego) na typ całkowity.
float fmod(float, float)
Reszta z dzielenia zmiennoprzecinkowego.
float log10(float)
float log(float)
float exp(float)
float pow(float, float)
float sqrt(float)
Logarytm dziesiętny, logarytm naturalny, funkcja wykładnicza (e do potęgi), potęga i pierwiastek kwadratowy.
Błędy niestety są nieuniknione i dlatego warto poświęcić im trochę miejsca. Po wystąpieniu błędu w programie (w tym błędu podczas wczytywania, kompilowania czy wykonywania skryptu) program natychmiast kończy działanie i wyświetla treść błędu w pliku tekstowym. Jeśli skrypt się zapętli, program się zawiesi.
W pokazywanym po wystąpieniu błędu komunikacie numer linii skryptu odnosi się niestety do numeru linii względem początku drugiej części pliku AK (tej ze skryptem), a nie względem początku całego pliku AK. Niestety też, błąd kompilacji skryptu często nie zawiera treści i można się tylko domyślać (albo próbować komentować różne części kodu), w którym miejscu się znajduje i co jest źle.
Do dyspozycji skryptu program oddaje kilka funkcji związanych tematycznie z obsługą błędów:
void Beep(uint, uint)
Wydobywa z systemowego głośniczka (PC Speakera) dźwięk o podanej częstotliwości (w Hz) i czasie trwania (w ms). Na czas odgrywania dźwięku cały program zawiesza działanie, dlatego czas ten nie może być długi, a tej funkcji należy używać wyłącznie do celów debugowania.
Przykład:
Beep(1000, 100);
void Debug(int, int)
Wyświetla na pasku podpowiedzi w oknie programu podane dwie liczby całkowite. Będą tam widoczne do czasu ich zastąpienia przez nowy napis w wyniku ponownego wywołania tej funkcji, wyświetlenia podpowiedzi do kontrolki pod kursorem myszy itp.
void error(int)
Wywołuje błąd. Kończy działanie programu pozwalając przekazać kod błędu jako liczbę całkowitą.
Przykład:
if (count <= 0) error(13);
void assert(bool)
Wywołuje błąd, jeśli podana w nawiasie wartość logiczna albo wyrażenie nie jest prawdziwe. Służy do sprawdzania asercji, czyli warunków, które bezwzględnie powinny być spełnione w danym miejscu.
Przykład:
assert(count > 0);
Podstawą działania automatu komórkowego są oczywiście komórki. Strukturą danych, na której się operuje jest 2-wymiarowa tablica takich komórek. W celu zapewnienia dużej elastyczności, a zarazem dostatecznej wydajności nie ma możliwości definiowania własnego formatu komórki. W zamian za to, każda komórka składa się z wielu pól różnego rodzaju, które są do dyspozycji i mogą być dowolnie wykorzystywane. Są to:
b1, b2, b3, b4 - 4 składniki typu bool
u1, u2, u3, u4 - 4 składniki typu uint
f1, f2, f3, f4 - 4 składniki typu float
Po uruchomieniu programu, a, przed wywołaniem funkcji OnInit()
ze skryptu cała tablica inicjowana jest zerami:
bool mają wartość false
uint mają wartość 0
float mają wartość 0.0
W razie potrzeby można dokonać własnego wypełnienia wstępnego w funkcji
OnInit().
Tablica komórek jest 2-wymiarowa, a jej szerokość i wysokość nie może ulegać zmianom w czasie działania programu. Te wymiary podaje się w pierwszej części pliku AK w sekcjach:
Width, Height
Obowiązkowe
Typ: liczba całkowita (większa od zera, rozsądnie mała)
Określają szerokość i wysokość tablicy komórek.
Przykład:
Width = 100 Height = 75
Dla skryptu dostępne są funkcje do odczytywania szerokości i wysokości tablicy. Warto ich używać zamiast wpisywać te wymiary do skryptu na sztywno powielając je względem określenia szerokości i wysokości w części I pliku AK. Dzięki temu w przypadku zmiany tych parametrów wystarczy je zmienić tylko tam.
int GetWidth()
int GetHeight()
Zwracają odpowiednio szerokość i wysokość tablicy w komórkach.
Komórki w tablicy indeksowane są od współrzędnych (x,y)
równych (0,0) w prawo i w dół. Ostatnia komórka - ta po prawej
na dole - ma więc współrzędne (GetWidth()-1, GetHeight()-1).
Zapisywanie do komórek poza zakresem powoduje błąd.
Dla uproszczenia pisania skryptów gwarantowane jest natomiast, że odczytywanie komórek z dowolnych współrzędnych, także spoza zakresu, jest zawsze poprawne. Zwracana wartość spoza zakresu jest brana z tablicy po zawinięciu jej (można to sobie wyobrazić jako sąsiadujące rozłożenie tablicy w poziomie i w pionie powtórzonej na całej powierzchni współrzędnych nieskończenie wiele razy) lub brana jest pewna wartość domyślna, zależna od przedstawionych niżej ustawień.
Współrzędne są typu int, a więc
typu całkowitego ze znakiem. Dzięki temu mogą być też ujemne.
Wrap
Obowiązkowa
Typ: logiczny
true - podczas odczytywania komórek spoza zakresu zwracane
są wartości z tablicy po zawinięciu (powtórzeniu)
false - podczas odczytywania komórek spoza zakresu zwracana
jest wartość domyślna
Przykład:
Wrap = true
Default
Opcjonalna
Musi wystąpić poniżej Wrap
Może wystąpić tylko, jeśli Wrap = false
Określa wartość domyślną komórek, używaną podczas odczytywania komórek spoza zakresu tablicy.
Struktura: sekcja [], a w niej dowolne spośród składowych
komórki i ich wartości odpowiedniego typu, w dowolnej kolejności.
Domyślnie:
b1,b2,b3,b4 = false u1,u2,u3,u4 = 0 f1,f2,f3,f4 = 0.0
Przykład:
Default = [ b1 = false f1 = 0.02 u3 = 0 u4 = 10 ]
Tablicę komórek nazywamy buforem. Program może stosować buforowanie pojedyncze, podwójne lub potrójne.
Buffers
Obowiązkowa
Wartość: 1, 2, 3
Określa rodzaj buforowania - ilu buforów ma używać program.
Przykład:
Buffers = 2
Buforowanie pojedyncze polega na tym, że program przechowuje tylko jedną kopię tablicy komórek. Wszelkie operacje odczytywania i zapisywania są przeprowadzana bezpośrednio na niej. Programowany z użyciem pojedynczego buforowania automat komórkowy ma to do siebie, że przeliczając kolejne komórki i korzystając przy tym z wartości komórek sąsiednich odczytuje się te zmienione już wartości.
Buforowanie podwójne polega na tym, że program przechowuje dwie kopie tablicy. Zapis odbywa się do bufora pierwszego, a odczyt z bufora drugiego. Dzięki temu podczas przeliczania odczytywane są wartości komórek sąsiednich jeszcze sprzed zmodyfikowania w danej iteracji.
Program sam zarządza buforami tak, że po każdej iteracji jej wyniki z bufora pierwszego są kopiowane do drugiego i będą odczytywane jako "stare" wartości podczas przeliczania w iteracji następnej.
Nie trzeba zapisywać każdej komórki tablicy w każdej nowej iteracji. Komórki tablicy pierwszej (tej do zapisywania) mają na początku każdej iteracji te same wartości, jak komórki tablicy drugiej (tej, z której się odczytuje). Dzięki temu niezapisanie pewnej komórki po prostu pozostawia jej wartość bez zmian.
Buforowanie potrójne działa tak jak podwójne, ale program
przechowuje dodatkowo kopię tablicy z wynikami obliczeń poprzedniej
iteracji. Nie ma dostępu do tych wartości. Mogą one posłużyć jedynie do
wizualizacji pól typu bool - do zaznaczenia na inny kolor
komórek, które były i są aktywne, były i są nieaktywne, a na inny tych, które
stały się lub przestały być aktywne względem poprzedniej iteracji.
Wszystko to odbywa się w sposób przezroczysty. Program sam zarządza buforami. W skrypcie używa się buforów niezależnie od wybranego trybu buforowania.
void SetBuffer_b1(int, int, bool)
void SetBuffer_b2(int, int, bool)
void SetBuffer_b3(int, int, bool)
void SetBuffer_b4(int, int, bool)
void SetBuffer_u1(int, int, uint)
void SetBuffer_u2(int, int, uint)
void SetBuffer_u3(int, int, uint)
void SetBuffer_u4(int, int, uint)
void SetBuffer_f1(int, int, float)
void SetBuffer_f2(int, int, float)
void SetBuffer_f3(int, int, float)
void SetBuffer_f4(int, int, float)
bool GetBuffer_b1(int, int)
bool GetBuffer_b2(int, int)
bool GetBuffer_b3(int, int)
bool GetBuffer_b4(int, int)
uint GetBuffer_u1(int, int)
uint GetBuffer_u2(int, int)
uint GetBuffer_u3(int, int)
uint GetBuffer_u4(int, int)
float GetBuffer_f1(int, int)
float GetBuffer_f2(int, int)
float GetBuffer_f3(int, int)
float GetBuffer_f4(int, int)
Funkcje do zapisywania i odczytywania poszczególnych składowych komórek tablicy (bufora) o podanych współrzędnych.
Stan tablicy może zostać zapisany do pliku lub wczytany z pliku.
Odbywa się to w sposób niezależny od cyklu przeliczeń i skrypt nie ma z tym
niczego wspólnego. Do wczytania tablicy z pliku służy polecenie
Wczytaj tablicę, a do zapisania tablicy do pliku -
Zapisz tablicę. Polecenia ta działają wyłącznie
w trybie okienkowym (nie na pełnym ekranie). Pokazują standardowe okna dialogowe
Windows do wyboru nazwy pliku i pozwalają wybrać jeden z dwóch formatów pliku:
Jeśli wymiary tablicy zapamiętanej we wczytywanym pliku nie odpowiadają wymiarom tablicy automatu wykonywanego aktualnie w programie, wartości komórek spoza zakresu zostaną zignorowane, a komórki niewczytane z pliku pozostaną bez zmian.
To plik binarny przechowujący zapamiętaną tablicę komórek w postaci nieskopresowanej. Rozpoczyna się od nagłówka w postaci 4 znaków:
AKN1
Dalej następuje szerokość i wysokość w postaci dwóch liczb całkowitych 32-bitowych.
Dalej ciągnie się tablica komórek. Ilość komórek to szerokość * wysokość, a każda z nich zajmuje 36 bajtów i składa się z takich pól:
b1, b2, b3, b4.
0 oznacza false, każda inna wartość (domyślnie 1) oznacza
true.
u1, u2, u3, u4.
f1, f2, f3, f4.
To plik binarny przechowujący zapamiętaną tablicę komórek w postaci skompresowanej. Ma budowę podobną do pliku AKN. Rozpoczyna się od nagłówka w postaci 4 znaków:
AKS1
Dalej jest szerokość i wysokość, podobnie jak w pliku AKN. Różni go fakt, że właściwa treść tablicy komórek jest zapisana w postaci strumienia danych skompresowanego za pomocą biblioteki zlib.
Najważniejszą rzeczą, obok obliczeń, jest wyświetlanie wyników. Sposób prezentacji tablicy przez program i dostępne w tym zakresie możliwości są kompromisem pomiędzy elastycznością, a wydajnością.
Background
Obowiązkowa
Typ: kolor
Określa kolor tła.
Przykład:
Background = blue
W tym miejscu jest najwyższy czas, aby opisać sposób zapisywania kolorów w części I pliku AK. Jako koloru można użyć jednej z 16 dostępnych nazw odpowiadającym standardowym kolorom HTML:
black
gray
silver
white
fuchsia
red
orange
yellow
lime
aqua
blue
purple
maroon
olive
green
teal
Można też używać wartości poszczególnych składowych RGB zapisanych w postaci
szesnastkowej, np. FFFFFF, 7FC0FF, 00FFFF
itp.
Dostępne jest kilka parametrów służących do regulacji sposobu wyświetlania tablicy. Można je zmieniać za pomocą odpowiednich kontrolek w czasie działania programu, a ich wartości początkowe można nadawać za pomocą opcjonalnych sekcji I części pliku AK.
Podstawowym parametrem jest powiększenie. Można go zmieniać w programie
za pomocą suwaka Zmień powiększenie. Kolorem żółtym
oznaczony jest na tym suwaku zakres, w którym obraz nie zmieści się już
w całości na ekranie przy aktualnej rozdzielczości czy wymiarach okna programu.
Suwak powiększenia jest w skali logarytmicznej. Powiększenie można zmieniać w szerokim zakresie. Znaczenie jego wartości jest inne zależnie od sposobu wizualizacji:
Do zresetowania powiększenia do wartości 100% służy przycisk
Zresetuj powiększenie.
Zoom
Opcjonalna
Typ: liczba rzeczywista
Domyślnie: 1.0
Określa początkowe powiększenie.
Przykład:
Zoom = 10
Jeśli automat komórkowy wizualizowany jest jako tekstura, dostępne są dwa dodatkowe parametry opisujące sposób jego wyświetlania przez program.
Pierwszy z nich to filtrowanie. Można je włączyć przyciskiem
Filtruj teksturę. Jeśli filtrowanie jest włączone,
program nakazuje samplerowi karty graficznej zastosowanie filtrowania liniowego.
Tekstura wygląda wtedy w powiększeniu na rozmytą, a nie są widoczne duże,
kwadratowe piksele. Ustawienie początkowe tej wartości opisuje następująca
sekcja:
Filtered
Opcjonalna
Typ: logiczny
Domyślnie: false
Określa, czy tekstura ma być filtrowana.
Przykład:
Filtered = true
Drugim parametrem wyświetlania tekstury jest rozkładanie sąsiadująco.
Jeśli włączone, tekstura nie jest wyświetlana w jednej kopii pośrodku okna
programu, ale jest powtarzana jedna obok drugiej w poziomie i w pionie
wypełniając cały dostępny obszar. Opcję tą można włączyć przyciskiem
Rozłóż teksturę sąsiadująco. Ustawienie
początkowe tej wartości opisuje następująca sekcja:
Tiled
Opcjonalna
Typ: logiczny
Domyślnie: false
Określa, czy tekstura ma być rozłożona sąsiadująco.
Przykład:
Tiled = true
Program może pracować w oknie lub w trybie pełnoekranowym.
Okno można swobodnie przesuwać, skalować, maksymalizować i minimalizować.
Aby przełączyć się w tryb pełnoekranowy, należy wybrać odpowiednie parametry
(rozdzielczość i częstotliwość odświeżania ekranu) za pomocą suwaka
Wybierz rozdzielczość, a następnie nacisnąć przycisk
Przełącz na pełny ekran lub z powrotem do okna.
Ponowne naciśnięcie tego przycisku spowoduje powrót do trybu okienkowego.
Przystąpię teraz do opisania najważniejszej chyba i najtrudniejszej części całego programu - ustawień wizualizacji Ustawienia te opisuje sekcja pliku AK o skomplikowanej składni:
View
Wymagana
Ta sekcja określa sposób wizualizacji danych. Nie ma chyba innego wyjścia, niż rozpisać jej składnię na poszczególne elementy:
cells), bądź jako strzałeczka (jak w
vectors), zależnie od wartości jednego z pól komórki typu
bool.
cells i vectors,
dowolna kolejność false i true, np.:
[ <b> false = cells <kolor> true = vectors <wektor> ]W ten sposób definiuje się pole typu
bool, od którego będzie
zależał rodzaj wizualizacji każdej komórki i jaka jego wartość logiczna
odpowiada jakiemu rodzajowi wizualizacji wraz z jego parametrami.
bool, którego wartości
logiczne będą odpowiadały dwóm możliwym stanom, zależnie od kontekstu
użycia tej wartości.
uint, którego wartości
będą się mieściły w podanym przedziale liczbowym. Jeśli wartości tego
pola będą wychodziły poza ten przedział, zachowanie jest niezdefiniowane
(np. mogą zostać ograniczone do minimalnej/maksymalnej wartości albo
strzałka może wychodzić poza swoje pole).
float, którego wartości
będą się mieściły w podanym przedziale liczbowym. Jeśli wartości tego
pola będą wychodziły poza ten przedział, zachowanie jest niezdefiniowane
(np. mogą zostać ograniczone do minimalnej/maksymalnej wartości albo
strzałka może wychodzić poza swoje pole).
bool.
bool w tablicy poprzedniej i aktualnej.
Tej opcji wolno używać tylko, kiedy zastosowane jest potrójne buforowanie.
Kolejne wartości kolorów odpowiadają odpowiednio:
false, wartość obecna false
false, wartość obecna true
true, wartość obecna false
true, wartość obecna true
uint interpretowane
bezpośrednio jako wartość koloru XRGB. Najstarszy bajt jest ignorowany,
pozostałe odpowiadają poszczególnym składowym koloru.
uint. Ta opcja przydatna jest
podczas wykorzystywania takiego pola jako wartości wyliczeniowej -
przyjmującej jedną z kilku wartości. Jeśli wartości nie ma na liście,
zachowanie jest niezdefiniowane.
bool -
b1, b2, b3, b4.
uint -
u1, u2, u3, u4.
float -
f1, f2, f3, f4.
Przykłady:
View = cells b1 [ false = 606060, true = white ]
Komórki wizualizowane będą jako kwadraciki o kolorze zależnym od pola
b1. Jeśli w danej komórce to pole ma wartość false,
do jej narysowania użyty zostanie kolor ciemny szary. Jeśli ma wartość
true, użyty zostanie kolor biały.
View = texture u1 [ RGB ]
Tablica rysowana będzie w postaci tekstury, a kolory jej pikseli będą
odpowiadały wartościom liczbowym pól u1 w poszczególnych
komórkach interpretowanym bezpośrednio jako wartość liczbowa koloru.
View = complex [
b1
true = cells
silver
false = vectors [
f1 [ rainbow 0.0 .. 1.0 ]
x = f2 [ -1.0 .. +1.0 ]
y = f3 [ -1.0 .. +1.0 ]
]
]
Jeśli dana komórka ma pole b1 równe true,
przedstawiana będzie jako kwadracik w kolorze jasnym szarym. Jeśli ma to pole
równe false, przedstawiana będzie jako strzałeczka, której kolor
ma być kolorem tęczy zależnym od pola f1 (które przyjmuje
wartości z zakresu od 0.0 - tej wartości będzie odpowiadał kolor
czerwony, do 1.0 - tej wartości będzie odpowiadał kolor niebieski),
a kierunek wektora opisują współrzędne: X podana w polu f2 i
Y podana w polu f3.
Choć program został tak skonstruowany, aby skrypt mógł po prostu operować na wartościach pól komórek, a sposób ich rysowania opisywała wyłącznie wizualizacja, jej możliwości mogą okazać się niewystarczające. Wówczas rozwiązaniem jest obliczanie koloru do rysowania komórek również za pomocą skryptu. Do pomocy przychodzą wtedy funkcje do operacji na kolorach:
uint InterpolateColor(uint, uint, float)
Zwraca kolor zinterpolowany pomiędzy dwoma podanymi, zależnie od wartości
liczbowej trzeciego parametru z zakresu od 0.0 do 1.0.
Najstarszy bajt zwracanego koloru oficjalnie jest niezdefiniowany.
Nieoficjalnie, w obecnej wersji jest zawsze równy 0xFF :)
Przykład:
InterpolateColor(0x000000, 0xFFFFFF, 0.5)
Zwraca kolor szary równo w połowie między czarnym, a białym.
uint RainbowColor(float)
Zwraca jeden z kolorów tęczy zależnie od podanej wartości liczbowej
z zakresu od 0.0 do 1.0.
Najstarszy bajt zwracanego koloru oficjalnie jest niezdefiniowany.
Nieoficjalnie, w obecnej wersji jest zawsze równy 0x00 :)
float Grayscale(uint)
Zwraca wartość z zakresu od 0.0 do 1.0
po przetworzeniu podanego koloru na odcień szarości.
Przykład:
SetBuffer_b1(x, y, Grayscale(GetBuffer_u1(x, y)) >= 0.5);
Ponieważ automaty komórkowe można stosować do generowania i przetwarzania grafiki, program wyposażony został w funkcje wczytywania i zapisywania obrazków w wielu formatach graficznych. Trzeba uczciwie przyznać, że nie wszystkie działają poprawnie. Częściowo jest to moja wina, ale częściowo też wina użytej biblioteki DevIL. Jednak te podstawowe - BMP, JPG, PNG, GIF itp. powinny raczej działać :)
Do wczytania obrazka służy polecenie
Wczytaj obrazek, a do zapisania -
Zapisz obrazek. Polecenia ta działają wyłącznie
w trybie okienkowym (nie na pełnym ekranie). Pokazują standardowe okna dialogowe
Windows do wyboru nazwy pliku i pozwalają wybrać jeden z wielu dostępnych
formatów graficznych (więcej jest obsługiwanych podczas wczytywania, niż
podczas zapisywania).
Wczytywanie i zapisywanie obrazków odbywa się w sposób przezroczysty tak, że skrypt nie musi mieć z tym niczego wspólnego. Jeśli wymiary obrazka z wczytywanego pliku nie odpowiadają wymiarom tablicy automatu wykonywanego aktualnie w programie, piksele spoza zakresu zostaną zignorowane, a piksele niewczytane z pliku pozostawią odpowiadające im komórki bez zmian.
Kolory pikseli obrazka są zapisywane z i wczytywane do składowej
u1 komórek tablicy. Jeśli w swoim automacie komórkowym używasz
innych pól i chcesz zrobić możliwość obsługi obrazków, musisz:
u1.
OnLoadImage() i
OnSaveImage().
void OnLoadImage()
Opcjonalnie udostępniana przez skrypt dla programu
Wywoływana jest po wczytaniu z obrazka.
Przykład:
Kolory pikseli są wczytywane z obrazka do u1.
Jeśli w automacie komórkowym używamy wartości logicznej pola b1,
musimy po wczytaniu obrazka przepisać wartości kolorów konwertując je na
wartości logiczne do tego używanego pola, np. na podstawie pewnego progu
jasności danego piksela:
void OnLoadImage()
{
int cx = GetWidth(), cy = GetHeight(), x, y;
for (y = 0; y < cy; y++) {
for (x = 0; x < cx; x++) {
SetBuffer_b1(x, y, Grayscale(GetBuffer_u1(x, y)) >= 0.5);
}
}
}
void OnSaveImage()
Opcjonalnie udostępniana przez skrypt dla programu
Wywoływana jest przed zapisaniem do obrazka.
Przykład:
Kolory pikseli są zapisywane do obrazka z u1.
Jeśli w automacie komórkowym używamy wartości logicznej pola b1,
musimy przed zapisaniem obrazka przepisać wartości logiczne do tego pola
konwertując je na kolory, np. jeden z dwóch kolorów:
void OnSaveImage()
{
int cx = GetWidth(), cy = GetHeight(), x, y;
for (y = 0; y < cy; y++) {
for (x = 0; x < cx; x++) {
if (GetBuffer_b1(x, y))
SetBuffer_u1(x, y, 0xFFFFFF);
else
SetBuffer_u1(x, y, 0);
}
}
}
Oprócz uruchamiania, zatrzymywania i regulowania prędkości wykonywania automatu komórkowego, użytkownik programu może wchodzić z nim w interakcje za pomocą narzędzi przygotowanych przez autora pliku AK. Narzędzia te mają postać dodatkowych przycisków oraz obsługi zdarzeń od myszki.
Autor pliku AK może oddać do dyspozycji dla użytkownika programu dodatkowe narzędzia w postaci przycisków obsługiwanych przez skrypt, których działanie dostosowane będzie do konkretnego automatu komórkowego. Przyciski te wyświetlane są po lewej stronie na pionowym pasku i nie mają ikon.
Aby stworzyć przyciski, należy przypisać im etykiety tekstowe za pomocą sekcji:
Buttons
Opcjonalna
Struktura: [ <numer> = <etykieta>, <numer> = <etykieta>, ... ]
Określa, które przyciski mają być widoczne i nadaje im etykiety tekstowe.
Przykład:
Buttons = [ 1 = "Wyczyść" 2 = "Zmień 10%" 3 = "Wypełnij 50%" 4 = "Rysuj 0" 5 = "Rysuj 1" 7 = "Linia" 8 = "Prostokąt" ]
Dostępnych przycisków jest 10 i są numerowane w sposób następujący:
1, 2, 3, 4, 5, 6, 7, 8, 9, 0. Aby obsłużyć ich naciśnięcie,
należy napisać w skrypcie wybrane z tych funkcji:
void OnButton1()
void OnButton2()
void OnButton3()
void OnButton4()
void OnButton5()
void OnButton6()
void OnButton7()
void OnButton8()
void OnButton9()
void OnButton0()
Opcjonalnie udostępniane przez skrypt dla programu
Wywoływane są po naciśnięciu odpowiedniego przycisku za pomocą myszki lub skrótu klawiszowego.
Przyciski mogą być wciśnięte lub niewciśnięte. Stanem tym można sterować za pomocą skryptu. Nie ma on żadnego wypływu na możliwość ich naciskania. Służy tylko do sygnalizacji wyboru użytkownika - grupa przycisków może służyć jako przełącznik pewnej wartości wyliczeniowej.
bool GetButtonDown(int)
void SetButtonDown(int, bool)
Zwraca lub ustawia stan wciśnięcia przycisku o podanym numerze.
Przykład:
bool CzymRysowac;
void OnButton4()
{
CzymRysowac = false;
SetButtonDown(4, true);
SetButtonDown(5, false);
}
void OnButton5()
{
CzymRysowac = true;
SetButtonDown(4, false);
SetButtonDown(5, true);
}
Uzupełnieniem narzędzi użytkownika jest możliwość obsługiwania zdarzeń od myszki. Za ich pomocą można tworzyć nawet zaawansowane narzędzia do interakcji z automatem komórkowym, na przykład do rysowania na tablicy punktów, linii czy prostokątów poprzez dowolne zmienianie stanu wskazywanych komórek.
void OnMouseDown(const int, const int)
Opcjonalnie udostępniana przez skrypt dla programu
Wywoływana jest po wciśnięciu przez użytkownika lewego przycisku myszki nad obszarem tablicy. Przekazane zostają współrzędne wskazanej komórki tablicy.
void OnMouseUp(const int, const int)
Opcjonalnie udostępniana przez skrypt dla programu
Wywoływana jest po zwolnieniu przez użytkownika lewego przycisku myszki nad obszarem tablicy. Przekazane zostają współrzędne wskazanej komórki tablicy.
void OnMouseMove(const int, const int)
Opcjonalnie udostępniana przez skrypt dla programu
Wywoływana jest po przesunięciu kursora myszy nad obszarem tablicy, ale tylko, jeśli lewy przycisk myszy jest wciśnięty. Przekazane zostają współrzędne wskazywanej komórki tablicy.
Przykład:
int LastX, LastY;
void OnMouseDown(const int x, const int y)
{
// Zapamiętaj pozycję początkową
LastX = x; LastY = y;
}
void OnMouseUp(const int x, const int y)
{
// Narysuj prostokąt
int ix, iy;
int x1 = min_i(x, LastX), x2 = max_i(x, LastX);
int y1 = min_i(y, LastY), y2 = max_i(y, LastY);
for (iy = y1; iy <= y2; iy++) {
for (ix = x1; ix <= x2; ix++) {
SetBuffer_b1(ix, iy, true);
}
}
}
Zastanawiałem się nad strukturą tej dokumentacji i ostateczne zdecydowałem się zorganizować ją tak, jak zorganizowane są np. specyfikacje standardów HTML czy CSS wydane przez konsorcjum W3C. Dokument podzielony został tematycznie, a opisy poszczególnych elementów są porozrzucane po różnych jego miejscach związanych z danym tematem. Dlatego tekst uzupełniać musi systematyczny spis wszystkich takich elementów wraz z odsyłaczami do ich opisów. Oto on, w kolejności alfabetycznej:
| Background | Obowiązkowa |
| Buffers | Obowiązkowa |
| Buttons | Opcjonalna |
| Default | Opcjonalna |
| Filtered | Opcjonalna |
| Height | Obowiązkowa |
| Running | Opcjonalna |
| Speed | Opcjonalna |
| Tiled | Opcjonalna |
| Title | Obowiązkowa |
| View | Obowiązkowa |
| Width | Obowiązkowa |
| Wrap | Obowiązkowa |
| Zoom | Opcjonalna |
| void OnButton0() | Opcjonalna |
| void OnButton1() | Opcjonalna |
| void OnButton2() | Opcjonalna |
| void OnButton3() | Opcjonalna |
| void OnButton4() | Opcjonalna |
| void OnButton5() | Opcjonalna |
| void OnButton6() | Opcjonalna |
| void OnButton7() | Opcjonalna |
| void OnButton8() | Opcjonalna |
| void OnButton9() | Opcjonalna |
| void OnFrame() | Obowiązkowa |
| void OnInit() | Obowiązkowa |
| void OnLoadImage() | Opcjonalna |
| void OnMouseDown(const int, const int) | Opcjonalna |
| void OnMouseMove(const int, const int) | Opcjonalna |
| void OnMouseUp(const int, const int) | Opcjonalna |
| void OnSaveImage() | Opcjonalna |
float abs_f(float)
int abs_i(int)
float acos(float)
float asin(float)
void assert(bool)
float atan(float)
float atan2(float, float)
void Beep(uint, uint)
float ceil(float)
float cos(float)
float cosh(float)
void Debug(int, int)
float E()
void error(int)
float exp(float)
float fmod(float)
float floor(float)
bool GetBuffer_b1(int, int)
bool GetBuffer_b2(int, int)
bool GetBuffer_b3(int, int)
bool GetBuffer_b4(int, int)
float GetBuffer_f1(int, int)
float GetBuffer_f2(int, int)
float GetBuffer_f3(int, int)
float GetBuffer_f4(int, int)
uint GetBuffer_u1(int, int)
uint GetBuffer_u2(int, int)
uint GetBuffer_u3(int, int)
uint GetBuffer_u4(int, int)
bool GetButtonDown(int)
int GetHeight()
int GetWidth()
float Grayscale(uint)
uint InterpolateColor(uint, uint, float)
float log10(float)
float log(float)
int max_i(int, int)
float max_f(float, float)
uint max_u(uint, uint)
int min_i(int, int)
float min_f(float, float)
uint min_u(uint, uint)
int minmax_i(int, int, int)
float minmax_f(float, float, float)
uint minmax_u(uint, uint, uint)
float PI()
float pow(float, float)
uint RainbowColor(float)
bool rand_b()
float rand_f()
float rand_f_range(float, float)
float rand_normal(float)
uint rand_u()
uint rand_u_range(uint, uint)
int round(float)
void SetBuffer_b1(int, int, bool)
void SetBuffer_b2(int, int, bool)
void SetBuffer_b3(int, int, bool)
void SetBuffer_b4(int, int, bool)
void SetBuffer_f1(int, int, float)
void SetBuffer_f2(int, int, float)
void SetBuffer_f3(int, int, float)
void SetBuffer_f4(int, int, bfloatool)
void SetBuffer_u1(int, int, uint)
void SetBuffer_u2(int, int, uint)
void SetBuffer_u3(int, int, uint)
void SetBuffer_u4(int, int, uint)
void SetButtonDown(int, bool)
float sin(float)
float sinh(float)
float sqrt(float)
float tan(float)
float tanh(float)
Filtruj teksturę
Przełącz na pełny ekran lub z powrotem do okna
Rozłóż teksturę sąsiadująco
Uruchom
Wczytaj obrazek
Wczytaj tablicę
Wybierz rozdzielczość
Wykonaj jeden krok
Wykonaj daną liczbę iteracji
Wyzeruj licznik iteracji
Zakończ działanie programu
Zapisz obrazek
Zapisz tablicę
Zatrzymaj
Zmień powiększenie
Zmień prędkość
Zresetuj powiększenie